home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / MailManager / XTextPkg.m < prev    next >
Encoding:
Text File  |  1992-09-28  |  29.2 KB  |  1,241 lines

  1. /*    This file is part of the XText package (version 0.8)
  2.     Mike Dixon, April 1992
  3.     
  4.     Last Modified: Mark Crispin 28 September 1992
  5.  
  6.     Copyright (c) 1992 Xerox Corporation.  All rights reserved.
  7.  
  8.     Use and copying of this software and preparation of derivative works based
  9.     upon this software are permitted.  This software is made available AS IS,
  10.     and Xerox Corporation makes no warranty about the software or its
  11.     performance.
  12. */
  13.  
  14. #import "XTextPkg.h"
  15.  
  16. // (beginning of ErrorStream.m)
  17. #import <appkit/Application.h>
  18. #import <appkit/Panel.h>
  19. #import <stdio.h>
  20.  
  21. @implementation ErrorStream
  22.  
  23. static id default_error_stream = 0;
  24.  
  25. + default
  26. {
  27.     if (!default_error_stream)
  28.         default_error_stream = [[ErrorStream allocFromZone:[NXApp zone]] init];
  29.     return default_error_stream;
  30. }
  31.  
  32. - report: (const STR) msg
  33. {
  34.     NXRunAlertPanel("Error", "%s", 0, 0, 0, msg);
  35.     return self;
  36. }
  37.  
  38. @end
  39. // (end of ErrorStream.m)
  40. // (beginning of XTAction.m)
  41. #import <string.h>
  42.  
  43. @implementation XTAction
  44.  
  45. static id undefined_action = 0;
  46.  
  47. + undefinedAction
  48. {
  49.     if (!undefined_action)
  50.         undefined_action = [[XTAction allocFromZone:[NXApp zone]] init];
  51.     return undefined_action;
  52. }
  53.  
  54. - applyTo:xtext event:(NXEvent *)event
  55. {
  56.     [xtext unboundKey];
  57.     return self;
  58. }
  59.  
  60. @end
  61.  
  62. @implementation XTMsg0Action
  63.  
  64. - initSel:(SEL)sel
  65. {
  66.     [super init];
  67.     action_sel = sel;
  68.     return self;
  69. }
  70.  
  71. - applyTo:xtext event:(NXEvent *)event
  72. {
  73.     return [xtext perform:action_sel];
  74. }
  75.  
  76. @end
  77.  
  78. @implementation XTMsg1Action
  79.  
  80. - initSel:(SEL)sel arg:(int)arg
  81. {
  82.     [super init];
  83.     action_sel = sel;
  84.     action_arg = arg;
  85.     return self;
  86. }
  87.  
  88. - applyTo: xtext event:(NXEvent *)event
  89. {
  90.     return [xtext perform:action_sel with:(id)action_arg];
  91. }
  92.  
  93. @end
  94.  
  95. @implementation XTMsg2Action
  96.  
  97. - initSel:(SEL)sel arg:(int)arg1 arg:(int)arg2
  98. {
  99.     [super init];
  100.     action_sel = sel;
  101.     action_arg1 = arg1;
  102.     action_arg2 = arg2;
  103.     return self;
  104. }
  105.  
  106. - applyTo: xtext event:(NXEvent *)event
  107. {
  108.     return [xtext perform:action_sel
  109.                     with:(id)action_arg1 with:(id)action_arg2];
  110. }
  111.  
  112. @end
  113.  
  114. @implementation XTDispatchAction
  115.  
  116. - initBase:(const char *)base estream:errs
  117. {
  118.     keyCode k;
  119.  
  120.     [super init];
  121.     for (k=0; k<KEY_CODES; ++k)
  122.         actions[k] = nil;
  123.     if (!strcmp(base,"none")) {}
  124. //    else if (!strcmp(base,"other_base"))
  125. //        initbase_other_base(actions)
  126.     else {
  127.         if ((*base != 0) && strcmp(base,"emacs")) {
  128.             char msg[100];
  129.             
  130.             sprintf(msg, "Unknown base '%.32s', using emacs instead", base);
  131.             [(errs ? errs : [ErrorStream default]) report:msg];
  132.         }
  133.         initbase_emacs(actions, [self zone]);
  134.     }
  135.     return self;
  136. }
  137.  
  138. - bindKey:(keyCode)key toAction:action estream:errs
  139. {
  140.     if ((key < 0) || (key >= KEY_CODES)) {
  141.         char msg[40];
  142.         sprintf(msg, "Invalid key code: %d", key);
  143.         [(errs ? errs : [ErrorStream default]) report:msg];
  144.     } else
  145.         actions[key] = action;
  146.     return self;
  147. }
  148.  
  149. - applyTo:xtext event:(NXEvent *)event
  150. {
  151.     keyCode k = (event->data.key.keyCode << 4);
  152.     id action;
  153.  
  154.     if ((k >= 0) && (k < KEY_CODES)) {
  155.         if (event->flags & NX_CONTROLMASK)   k += 1;
  156.         if (event->flags & NX_SHIFTMASK)     k += 2;
  157.         if (event->flags & NX_ALTERNATEMASK) k += 4;
  158.         if (event->flags & NX_COMMANDMASK)   k += 8;
  159.         action = actions[k];
  160.         if (action)
  161.             return [action applyTo:xtext event:event];
  162.     }
  163.     return nil;
  164. }
  165.  
  166. @end
  167.  
  168. @implementation XTEventMsgAction
  169.  
  170. - initSel:(SEL)sel
  171. {
  172.     [super init];
  173.     action_sel = sel;
  174.     return self;
  175. }
  176.  
  177. - applyTo:xtext event:(NXEvent *)event
  178. {
  179.     return [xtext perform:action_sel with:(id)event];
  180. }
  181.  
  182. @end
  183.  
  184. @implementation XTSeqAction
  185.  
  186. - initLength:(int)len actions:(XTAction **)acts
  187. {
  188.     [super init];
  189.     length = len;
  190.     actions = acts;
  191.     return self;
  192. }
  193.  
  194. - applyTo:xtext event:(NXEvent *)event
  195. {
  196.     int i;
  197.  
  198.     for (i=0; i<length; ++i)
  199.         [actions[i] applyTo:xtext event:event];
  200.     return self;
  201. }
  202.  
  203. @end
  204. // (end of XTAction.m)
  205. // (beginning of XText0.m)
  206. #import <objc/objc.h>
  207. #import <appkit/Window.h>
  208. #import <appkit/publicWraps.h>
  209.  
  210. @implementation XText0
  211.  
  212. struct WindowGuts { @defs(Window); }; 
  213.  
  214. /*    everything except the setInitialAction/setErrorStream is copied straight
  215.     out of Window's getFieldEditor:for: method
  216. */
  217.  
  218. + newFieldEditorFor:win initialAction:action estream:errs
  219. {
  220.     id editor = nil;
  221.     
  222.     if ([win isKindOf: [Window class]]) {
  223.         editor = ((struct WindowGuts *)win)->fieldEditor;
  224.         if (editor == nil) {
  225.             editor = [[self allocFromZone:[win zone]] init];
  226.             [editor setCharFilter: &NXFieldFilter];
  227.             [editor setSelectable: YES];
  228.             [editor setFontPanelEnabled: NO];
  229.             [editor setInitialAction:action];
  230.             if (errs)
  231.                 [editor setErrorStream:errs];
  232.             ((struct WindowGuts *)win)->fieldEditor = editor;
  233.         }
  234.     }
  235.     return editor;
  236. }
  237.  
  238. - initFrame:(const NXRect *)frameRect text:(const STR)theText
  239.     alignment:(int)mode
  240. {
  241.     [super initFrame:frameRect text:theText alignment:mode];
  242.     nextAction = nil;
  243.     initialAction = nil;
  244.     errorStream = [ErrorStream default];
  245.     return self;
  246. }
  247.  
  248. /*    We want to make sure that unrecognized messages are reported nicely,
  249.     since it's easy to generate them while your experimenting.
  250. */
  251.  
  252. - doesNotRecognize:(SEL)sel
  253. {
  254.     char msg[100];
  255.  
  256.     sprintf(msg, "No method for %.48s on this text object", sel_getName(sel));
  257.     [errorStream report: msg];
  258.     return self;
  259. }
  260.  
  261. /*    Egregious paranoia: don't set the error stream to something that can't
  262.     report errors...
  263. */
  264.  
  265. - setErrorStream:errs
  266. {
  267.     if ([errs respondsTo: @selector(report:)])
  268.         errorStream = errs;
  269.     else
  270.         [errorStream report:"Invalid argument to setErrorStream:"];
  271.     return self;
  272. }
  273.  
  274. - errorStream
  275. {
  276.     return errorStream;
  277. }
  278.  
  279. - setInitialAction:action
  280. {
  281.     initialAction = nextAction = action;
  282.     return self;
  283. }
  284.  
  285. - initialAction
  286. {
  287.     return initialAction;
  288. }
  289.  
  290. - setNextAction:action
  291. {
  292.     nextAction = action;
  293.     return self;
  294. }
  295.  
  296. - keyDown:(NXEvent *)event
  297. {
  298.     id temp;
  299.  
  300.     temp = nextAction;
  301.     nextAction = initialAction;
  302.     if (temp) {
  303.         temp = [temp applyTo:self event:event];
  304.         if (vFlags.disableAutodisplay) {
  305.             [self setAutodisplay:YES];
  306.             [self display];
  307.         }
  308.         if (sp0.cp == spN.cp)
  309.             [self setSel:sp0.cp :sp0.cp];    // hack to make caret reappear
  310.     }
  311.     return temp ? self : [super keyDown:event];
  312. }
  313.  
  314. - unboundKey
  315. {
  316.     NXBeep();
  317.     return self;
  318. }
  319.  
  320. - disableAutodisplay
  321. {
  322.     // There seems to be a bug with turning off autodisplay in text fields,
  323.     // so try to avoid doing it.
  324.     if (charFilterFunc != &NXFieldFilter)
  325.         [self setAutodisplay:NO];
  326.     return self;
  327. }
  328.  
  329. @end
  330. // (end of XText0.m)
  331. // (beginning of XText.m)
  332. #import <appkit/ClipView.h>
  333.  
  334. /*    Some of this code is based on other emacs-like Text classes by
  335.     Julie Zelenski, Lee Boynton, and Glen Diener.
  336.  
  337.     There's some ugly hair in here; the Text object is not very well
  338.     designed to support this kind of stuff.  No doubt this will all be
  339.     fixed by NextStep 9.0 ...
  340. */
  341.  
  342. /*    We use an undocumented (but very useful) method on ClipView; declare it
  343.     here to stop the compiler from complaining.
  344. */
  345.  
  346. @interface ClipView(undocumented)
  347. - _scrollTo:(NXPoint *)origin;
  348. @end
  349.  
  350. @implementation XText
  351.  
  352. - initFrame:(const NXRect *)frameRect text:(const STR)theText
  353.     alignment:(int)mode
  354. {
  355.     // i don't understand why the compiler whines without the (char *) here
  356.     [super initFrame:frameRect text:(char *)theText alignment:mode];
  357.     posHint = -1;
  358.     xHintPos = -1;
  359.     return self;
  360. }
  361.  
  362. - goto:(int)pos end:(int)end mode:(int)mode
  363. {
  364.     int start;
  365.     
  366.     switch(mode) {
  367.  
  368.     case 0:        // move
  369.         [self setSel:pos :pos];
  370.         [self scrollSelToVisible];
  371.         posHint = -1;
  372.         break;
  373.  
  374.     case 1:        // delete
  375.     case 2:        // cut
  376.         if (pos != end) {
  377.             start = pos;
  378.             if (start > end)
  379.                 { start = end; end = pos; }
  380.             [self disableAutodisplay];
  381.             [self setSel:start :end];
  382.             if (mode == 1)
  383.                 [self delete:self];
  384.             else
  385.                 [self cut:self];
  386.         }
  387.         posHint = -1;
  388.         break;
  389.  
  390.     case 3:        // select
  391.         start = pos;
  392.         if (start > end)
  393.             { start = end; end = pos; }
  394.         // The Text object can't even extend the selection without flashing,
  395.         // unless we disable autodisplay
  396.         if (sp0.cp != spN.cp)
  397.             [self disableAutodisplay];
  398.         [self setSel:start :end];
  399.         posHint = pos;
  400.         break;
  401.     }
  402.     xHintPos = -1;
  403.     return self;
  404. }
  405.  
  406. - moveChar:(int)cnt mode:(int)mode
  407. {
  408.     int pos, end;
  409.     int max = [self textLength];
  410.     
  411.     if (sp0.cp == posHint) {
  412.         pos = sp0.cp + cnt;
  413.         end = spN.cp;
  414.     } else {
  415.         pos = spN.cp + cnt;
  416.         end = sp0.cp;
  417.     }
  418.     if (pos < 0)
  419.         pos = 0;
  420.     else if (pos > max)
  421.         pos = max;
  422.     return [self goto:pos end:end mode:mode];
  423. }
  424.  
  425. /* NXBGetc - a text stream macro to get the previous character. */
  426.  
  427. typedef struct {
  428.     id text;
  429.     NXTextBlock *block;
  430. } textInfo;
  431.  
  432. static char getPrevious(NXStream *s)
  433. {
  434.     textInfo *info = (textInfo *) s->info;
  435.     NXTextBlock *block = info->block->prior;
  436.  
  437.     if (!block)
  438.         return EOF;
  439.     s->buf_base = block->text;
  440.     s->buf_ptr = s->buf_base + block->chars;
  441.     s->offset -= block->chars;
  442.     info->block = block;
  443.     return *(--s->buf_ptr);
  444. }
  445.  
  446. #define NXBGetc(s) \
  447.     (((s)->buf_base == (s)->buf_ptr) ? getPrevious(s) : \
  448.                                        *(--((s)->buf_ptr)) )
  449.  
  450. - moveWord:(int)cnt mode:(int)mode
  451. {
  452.     NXStream *s = [self stream];
  453.     char c;
  454.     int i, pos, end;
  455.     unsigned char digit_cat = charCategoryTable['0'];
  456.     unsigned char alpha_cat = charCategoryTable['a'];
  457.     unsigned char c_cat;
  458.     BOOL inWord = NO;
  459.  
  460.     if (cnt == 0)
  461.         return self;
  462.     if (sp0.cp == posHint) {
  463.         pos = sp0.cp;
  464.         end = spN.cp;
  465.     } else {
  466.         pos = spN.cp;
  467.         end = sp0.cp;
  468.     }
  469.     NXSeek(s, pos, NX_FROMSTART);
  470.     i = (cnt<0 ? -cnt : cnt);
  471.     while (1) {
  472.         c = (cnt<0 ? NXBGetc(s) : NXGetc(s));
  473.         if (c == EOF) break;
  474.         c_cat = charCategoryTable[c];
  475.         if (c_cat==alpha_cat || c_cat==digit_cat)
  476.             inWord = YES;
  477.         else if (inWord) {
  478.             --i;
  479.             if (i > 0)
  480.                 inWord = NO;
  481.             else
  482.                 break;
  483.         }
  484.     }
  485.     pos = NXTell(s);
  486.     if (c != EOF)
  487.         pos += (cnt<0 ? 1 : -1);
  488.     return [self goto:pos end:end mode:mode];
  489. }
  490.  
  491. /*  line is from an NXSelPt; returns the length of the line.
  492.     too bad this requires grunging in Text's data structures */
  493.  
  494. #define LINE_LENGTH(line) \
  495.     (self->theBreaks->breaks[(line)/sizeof(NXLineDesc)] & 0x3fff)
  496.  
  497. - moveLine:(int)cnt mode:(int)mode
  498. {
  499.     int pos, end, x, dir;
  500.  
  501.     if (sp0.cp == posHint) {
  502.         pos = sp0.cp;
  503.         end = spN.cp;
  504.     } else {
  505.         pos = spN.cp;
  506.         end = sp0.cp;
  507.     }
  508.     if (mode != 0)
  509.         [self disableAutodisplay];
  510.     // collapse and normalize the selection
  511.     [self setSel:pos :pos];
  512.     x = (sp0.cp == xHintPos ? xHint : (sp0.cp - sp0.c1st));
  513.     
  514.     if (cnt < 0) {
  515.         dir = NX_UP;
  516.         cnt = -cnt;
  517.     } else {
  518.         dir = NX_DOWN;
  519.     }
  520.     for (; cnt > 0; --cnt)
  521.         [self moveCaret: dir];
  522.  
  523.     pos = LINE_LENGTH(sp0.line)-1;
  524.     if (x < pos)
  525.         pos = x;
  526.     pos += sp0.c1st;
  527.     [self goto:pos end:end mode:mode];
  528.     xHintPos = pos;
  529.     xHint = x;
  530.     return self;
  531. }
  532.  
  533. - lineBegin:(int)mode
  534. {
  535.     int pos, end;
  536.  
  537.     if (sp0.cp == posHint) {
  538.         pos = sp0.c1st;
  539.         end = spN.cp;
  540.     } else {
  541.         pos = spN.c1st;
  542.         // Text is inconsistent about what line it thinks we're on
  543.         if (spN.cp == (spN.c1st + LINE_LENGTH(spN.line)))
  544.             pos = spN.cp;
  545.         end = sp0.cp;
  546.     }
  547.     return [self goto:pos end:end mode:mode];
  548. }
  549.  
  550. - lineEnd:(int)mode
  551. {
  552.     NXSelPt *sp;
  553.     int pos, end;
  554.  
  555.     if (sp0.cp == posHint) {
  556.         sp = &sp0;
  557.         end = spN.cp;
  558.     } else {
  559.         // need to correct for TBD
  560.         sp = &spN;
  561.         end = sp0.cp;
  562.     }
  563.     pos = sp->c1st + LINE_LENGTH(sp->line) - 1;
  564.     if (pos < sp->cp) {
  565.         // Text is being flakey again; we really want to be on the next line
  566.         // this is pretty gross
  567.         pos = sp->line;
  568.         if (theBreaks->breaks[pos/sizeof(NXLineDesc)] < 0)
  569.             pos += sizeof(NXHeightChange);
  570.         else
  571.             pos += sizeof(NXLineDesc);
  572.         pos = sp->cp + LINE_LENGTH(pos) - 1;
  573.     }
  574.     if ((pos == sp->cp) && (mode != 0))
  575.         ++pos;
  576.     return [self goto:pos end:end mode:mode];
  577. }
  578.  
  579. - docBegin:(int)mode
  580. {
  581.     return [self goto:0
  582.                  end:(sp0.cp == posHint ? spN.cp : sp0.cp)
  583.                  mode:mode];
  584. }
  585.  
  586. - docEnd:(int)mode
  587. {
  588.     return [self goto:[self textLength]
  589.                  end:(sp0.cp == posHint ? spN.cp : sp0.cp)
  590.                  mode:mode];
  591. }
  592.  
  593. - collapseSel:(int)dir
  594. {
  595.     int pos;
  596.  
  597.     if ((dir < 0) || ((dir == 0) && (sp0.cp == posHint)))
  598.         pos = sp0.cp;
  599.     else
  600.         pos = spN.cp;
  601.     return [self goto:pos end:pos mode:0];
  602. }
  603.  
  604. - transChars
  605. {
  606.     int pos = sp0.cp;
  607.     char buf[2], temp;
  608.  
  609.     if (pos == spN.cp) {
  610.         if (pos == (sp0.c1st + LINE_LENGTH(sp0.line) - 1))
  611.             --pos;
  612.         if (pos > 0)
  613.             if ([self getSubstring:buf start:pos-1 length:2] == 2) {
  614.                 temp = buf[1]; buf[1] = buf[0]; buf[0] = temp;
  615.                 [self disableAutodisplay];
  616.                 [self setSel:pos-1 :pos+1];
  617.                 [self replaceSel:buf length:2];
  618.                 return self;
  619.             }
  620.     }
  621.     NXBeep();
  622.     return self;
  623. }
  624.  
  625. - openLine
  626. {
  627.     int pos = sp0.cp;
  628.  
  629.     // don't do anything if there's a non-empty selection
  630.     if (pos == spN.cp) {
  631.         [self replaceSel:"\n"];
  632.         [self setSel:pos :pos];
  633.     } else
  634.         NXBeep();
  635.     return self;
  636. }
  637.  
  638. - scroll:(int)pages :(int)lines
  639. {
  640.     NXRect r;
  641.  
  642.     // if our superview isn't a ClipView, we can't scroll
  643.     if ([superview respondsTo:@selector(_scrollTo:)]) {
  644.         [superview getBounds:&r];
  645.         r.origin.y += pages*r.size.height + lines*[self lineHeight];
  646.         [superview _scrollTo:&r.origin];
  647.     } else
  648.         NXBeep();
  649.     return self;
  650. }
  651.  
  652. - scrollIfRO:(int)pages :(int)lines
  653. {
  654.     if (![self isEditable])
  655.         return [self scroll:pages :lines];
  656.     else
  657.         return nil;
  658. }
  659.  
  660. - insertChar:(NXEvent *)event
  661. {
  662.     char c;
  663.  
  664.     c = event->data.key.charCode;
  665.     [self replaceSel:&c length:1];
  666.     return self;
  667. }
  668.  
  669. - insertNextChar
  670. {
  671.     static id action = nil;
  672.  
  673.     if (!action)
  674.         action = [[XTEventMsgAction allocFromZone:[NXApp zone]]
  675.                             initSel:@selector(insertChar:)];
  676.     nextAction = action;
  677.     return self;
  678. }
  679.  
  680. @end
  681.  
  682. /*    A (not very elegant) table format for storing the initial emacs bindings.
  683.     Unused args are indicated by the magic value 99.
  684. */
  685.  
  686. typedef struct {
  687.     SEL   *sel;
  688.     short arg1;
  689.     short arg2;
  690.     keyCode key;
  691. } tbl_entry;
  692.  
  693. /*    For these and other key codes, refer to
  694.     /NextLibrary/Documentation/NextDev/Summaries/06_KeyInfo/KeyInfo.rtfd
  695. */
  696.  
  697. const tbl_entry emacs_base[] = {
  698. {&@selector(moveChar:mode:), -1,  0, 0x351},  // ctrl-b       move back char
  699. {&@selector(moveChar:mode:), -1,  1, 0x401},  // ctrl-h       delete back char
  700. {&@selector(moveChar:mode:), -1,  3, 0x353},  // ctrl-B       select back char
  701. {&@selector(moveChar:mode:),  1,  0, 0x3c1},  // ctrl-f       move fwd char
  702. {&@selector(moveChar:mode:),  1,  1, 0x3b1},  // ctrl-d       delete fwd char
  703. {&@selector(moveChar:mode:),  1,  3, 0x3c3},  // ctrl-F       select fwd char
  704. {&@selector(moveWord:mode:), -1,  0, 0x354},  // alt-b       move back word
  705. {&@selector(moveWord:mode:), -1,  1, 0x1b4},  // alt-del   delete back word
  706. {0,                              0,  0, 0x404},  // alt-h            (ditto)
  707. {&@selector(moveWord:mode:), -1,  3, 0x356},  // alt-B       select back word
  708. {&@selector(moveWord:mode:),  1,  0, 0x3c4},  // alt-f       move fwd word
  709. {&@selector(moveWord:mode:),  1,  1, 0x3b4},  // alt-d       delete fwd word
  710. {&@selector(moveWord:mode:),  1,  3, 0x3c6},  // alt-F       select fwd word
  711. {&@selector(moveLine:mode:), -1,  0, 0x081},  // ctrl-p       move back line
  712. {&@selector(moveLine:mode:), -1,  3, 0x083},  // ctrl-P       select back line
  713. {&@selector(moveLine:mode:),  1,  0, 0x371},  // ctrl-n       move fwd line
  714. {&@selector(moveLine:mode:),  1,  3, 0x373},  // ctrl-N       select fwd line
  715. {&@selector(lineBegin:),      0, 99, 0x391},  // ctrl-a       move to line begin
  716. {&@selector(lineBegin:),      3, 99, 0x393},  // ctrl-A       select to line bgn
  717. {&@selector(lineEnd:),          0, 99, 0x441},  // ctrl-e       move to line end
  718. {&@selector(lineEnd:),          1, 99, 0x3e1},  // ctrl-k       delete to line end
  719. {&@selector(lineEnd:),          3, 99, 0x443},  // ctrl-E       select to line end
  720. {&@selector(docBegin:),          0, 99, 0x2e6},  // alt-<       move to doc begin
  721. {&@selector(docEnd:),          0, 99, 0x2f6},  // alt->       move to doc begin
  722. {&@selector(collapseSel:),      0, 99, 0x381},  // ctrl-spc  collapse selection
  723. {&@selector(transChars),     99, 99, 0x481},  // ctrl-t    transpose chars
  724. {&@selector(setNextAction:),  0, 99, 0x421},  // ctrl-q       quote next key
  725. {&@selector(insertNextChar), 99, 99, 0x425},  // ctrl-alt-q   really quote key
  726. {&@selector(openLine),         99, 99, 0x071},  // ctrl-o       open line
  727. {&@selector(scroll::),          1, -1, 0x341},  // ctrl-v       scroll fwd page
  728. {0,                              0,  0, 0x0f6},  // alt-shft-down    (ditto)
  729. {&@selector(scroll::),         -1,  1, 0x344},  // alt-v       scroll back page
  730. {0,                              0,  0, 0x166},  // alt-shft-up    (ditto)
  731. {&@selector(scroll::),          0,  4, 0x343},  // ctrl-V       scroll fwd 4 lines
  732. {&@selector(scroll::),          0, -4, 0x346},  // alt-V       scroll back 4 lines
  733. {&@selector(scroll::),      -9999,  0, 0x165},  // alt-ctrl-up    scroll to start
  734. {&@selector(scroll::),       9999,  0, 0x0f5},  // alt-ctrl-down  scroll to end
  735. {&@selector(scrollIfRO::),      1, -1, 0x380},  // space       scroll fwd pg if RO
  736. {&@selector(scrollIfRO::),     -1,  1, 0x1b0},  // del       scroll back pg if RO
  737. {&@selector(scrollIfRO::),      0,  4, 0x382},  // shift-sp  scroll fwd 4 lines
  738. {&@selector(scrollIfRO::),      0, -4, 0x1b2},  // shift-del scroll back 4 lines
  739. {&@selector(scrollSelToVisible),
  740.                              99, 99, 0x2d1},  // ctrl-l       scroll to selection
  741. {0, 0, 0}
  742. };
  743.  
  744. void initbase_emacs(actionTbl actions, NXZone *zone)
  745. {
  746.     keyCode i;
  747.     const tbl_entry *e;
  748.     XTAction *a = [XTAction undefinedAction];
  749.  
  750.     // make all non-command control & alt combinations invoke "unboundKey"
  751.     for (i=0; i<KEY_CODES; i+=16) {
  752.         actions[i+1] = actions[i+3] = actions[i+4] = actions[i+5]
  753.             = actions[i+6] = actions[i+7] = a;
  754.     }
  755.  
  756.     // ... except for ctrl-i (a handy substitute for tab)
  757.     actions[6*16 + 1] = nil;
  758.  
  759.     // and then install the emacs key bindings
  760.     for (e=emacs_base; (e->key != 0); ++e) {
  761.         if (e->sel == 0) {}
  762.             // same action as previous binding
  763.         else if (e->arg1 == 99)
  764.             a = [[XTMsg0Action allocFromZone:zone] initSel:*(e->sel)];
  765.         else if (e->arg2 == 99)
  766.             a = [[XTMsg1Action allocFromZone:zone]
  767.                     initSel:*(e->sel) arg:e->arg1];
  768.         else
  769.             a = [[XTMsg2Action allocFromZone:zone]
  770.                     initSel:*(e->sel) arg:e->arg1 arg:e->arg2];
  771.         actions[e->key] = a;
  772.     }
  773. }
  774. // (end of XText.m)
  775. // (beginning of XTScroller.m)
  776.  
  777. @implementation XTScroller
  778.  
  779. - initFrame:(const NXRect *)frameRect
  780. {
  781.     NXRect rect = {0.0, 0.0, 0.0, 0.0};
  782.     NXSize s = {1.0E38, 1.0E38};
  783.     id my_xtext;
  784.     
  785.     // this is mostly cribbed from the TextLab example
  786.     // it's hard to believe that it needs to be this complicated
  787.  
  788.     [super initFrame:frameRect];
  789.     [[self setVertScrollerRequired:YES] setHorizScrollerRequired:NO];
  790.     [self setBorderType:NX_BEZEL];
  791.     
  792.     [self getContentSize:&(rect.size)];
  793.     my_xtext = [[XText alloc] initFrame:&rect];
  794.     [my_xtext setOpaque:YES];
  795.     [my_xtext notifyAncestorWhenFrameChanged:YES];
  796.     [my_xtext setVertResizable:YES];
  797.     [my_xtext setHorizResizable:NO];
  798.     [my_xtext setMonoFont:NO];
  799.     [my_xtext setDelegate:self];
  800.     
  801.     [my_xtext setMinSize:&(rect.size)];
  802.     [my_xtext setMaxSize:&s];
  803.     [my_xtext setAutosizing:NX_HEIGHTSIZABLE | NX_WIDTHSIZABLE];
  804.     
  805.     [my_xtext setCharFilter:NXEditorFilter];
  806.  
  807.     [self setDocView:my_xtext];
  808.     [my_xtext setSel:0 :0];    
  809.     
  810.     [contentView setAutoresizeSubviews:YES];
  811.     [contentView setAutosizing:NX_HEIGHTSIZABLE | NX_WIDTHSIZABLE];
  812.  
  813.     return self;
  814. }
  815.  
  816. @end
  817. // (end of XTScroller.m)
  818. // (beginning of XTAction_parser.m)
  819. #import <sys/types.h>
  820. #import <bsd/dev/m68k/keycodes.h>
  821. #import <stdlib.h>
  822.  
  823. /*  This file contains all the routines to parse the argument to the
  824.     addBindings:estream: method; it's the most complicated part of the
  825.     whole package.  The strategy is simple recursive descent, and we
  826.     make no attempt to recover from errors.
  827.  
  828.     The grammar supported is somewhat more general than necessary; for
  829.     example you can nest sequences of instructions, which currently serves
  830.     no purpose (unless you wanted to get around the maximum sequence
  831.     length...).  The idea is just to make it easy to add more complex
  832.     control structure later, if that turns out to be useful.
  833. */
  834.  
  835. #define MAX_SEQUENCE_LENGTH 16        //    max number of actions in a sequence
  836. #define MAX_SELECTOR_LENGTH 32        //    max length of a selector name
  837. #define MAX_STRING_LENGTH 256        //    max length of a string argument
  838. #define MAX_ARGS 2                    //    max number of args to a message
  839.     // (Note that if you increase MAX_ARGS you'll also have to add a new
  840.     //    subclass of XTAction and augment parse_msg to use it.)
  841.  
  842. #define MAX_KEYS 8                    //    max number of keys affected by a
  843.                                     //    single binding
  844.  
  845. #define PRE_ERROR_CONTEXT 32        //    number of characters displayed before
  846. #define POST_ERROR_CONTEXT 16        //    and after a syntax error
  847.  
  848. typedef keyCode keySet[MAX_KEYS];    //    set of keys an action will be bound to
  849.  
  850.  
  851. #define ALPHA(c) \
  852.     (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || (c == '_'))
  853.  
  854. #define WHITE(c) \
  855.     ((c == ' ') || (c == '\n') || (c == '\t') || (c == '\r'))
  856.  
  857. #define DIGIT(c) \
  858.     ((c >= '0') && (c <= '9'))
  859.  
  860.  
  861. /*    skip_whitespace advances over any white space and returns the first
  862.     non-whitespace character.
  863.  
  864.     Like the rest of the parsing routines, it's passed a pointer to a
  865.     char pointer, which is advanced as the string is consumed.
  866. */
  867.  
  868. char skip_whitespace(const char **p)
  869. {
  870.     while (WHITE(**p))
  871.         ++*p;
  872.     return **p;
  873. }
  874.  
  875. /*    report_syntax_error is used to report all syntax errors detected during
  876.     parsing.  The args are
  877.         error        optional information about the type of error
  878.         p            a pointer to the place at which the error was detected
  879.         start        a pointer to the beginning of the string being parsed
  880.         errs        the ErrorStream
  881.  
  882.     The message will contain some or all of the string surrounding the
  883.     error to help identify the problem.
  884. */
  885.  
  886. void report_syntax_error(const char *error, const char *p, const char *start,
  887.                          ErrorStream *errs)
  888. {
  889.     char msg[100+PRE_ERROR_CONTEXT+POST_ERROR_CONTEXT];
  890.     const char *prefix;
  891.     int  prefix_length;
  892.  
  893.     // display at most PRE_ERROR_CONTEXT characters before the error point...
  894.  
  895.     if (start < (p - PRE_ERROR_CONTEXT)) {
  896.         prefix = p - PRE_ERROR_CONTEXT;
  897.         prefix_length = PRE_ERROR_CONTEXT;
  898.     } else {
  899.         prefix = start;
  900.         prefix_length = p-start;
  901.     }
  902.  
  903.     // ... and at most POST_ERROR_CONTEXT characters after, except that if
  904.     // there weren't many characters before we can put even more after.
  905.  
  906.     sprintf(msg, "Syntax error%s in binding:\n    %.*s (here) %.*s",
  907.                 error, prefix_length, prefix,
  908.                 (PRE_ERROR_CONTEXT+POST_ERROR_CONTEXT-prefix_length), p);
  909.     [errs report:msg];
  910. }
  911.  
  912. XTAction *parse_action(const char **p, NXZone *z,
  913.                        const char *start, ErrorStream *errs);
  914.  
  915. /*    parse_seq parses a '{}'-delimited, ';'-separated sequence of actions
  916.     and constructs an XTSeqAction out of them.  The args are
  917.         p        pointer to the current position pointer
  918.         z        zone in which to allocate the XTActions
  919.         start    the beginning of the string (for reporting errors)
  920.         errs    the ErrorStream
  921.  
  922.     If there are no errors, the new XTAction is returned; otherwise the
  923.     result is nil.
  924. */
  925.  
  926. XTAction *parse_seq(const char **p, NXZone *z,
  927.                     const char *start, ErrorStream *errs)
  928. {
  929.     // we accumulate the actions in an array on the stack, and then copy
  930.     // them into the specified zone when we find out how many there were.
  931.  
  932.     XTAction *actions[MAX_SEQUENCE_LENGTH];
  933.     int num_actions = 0;
  934.     XTAction **copied_actions = 0;
  935.     char c;
  936.  
  937.     // skip over the open brace
  938.     ++*p;
  939.     while (1) {
  940.         c = skip_whitespace(p);
  941.         if (c == '}') {
  942.             ++*p;
  943.             if (num_actions == 1)
  944.                 return actions[0];
  945.             else if (num_actions > 0) {
  946.                 size_t size = num_actions * sizeof(XTAction *);
  947.                 copied_actions = NXZoneMalloc(z, size);
  948.                 memcpy(copied_actions, actions, size);
  949.             }
  950.             return [[XTSeqAction allocFromZone:z]
  951.                         initLength:num_actions actions:copied_actions];
  952.             }
  953.         else if (c == ';')
  954.             ++*p;
  955.         else {
  956.             if (num_actions >= MAX_SEQUENCE_LENGTH) {
  957.                 report_syntax_error(" (sequence too long)", *p, start, errs);
  958.                 return nil;
  959.             }
  960.             if (!(actions[num_actions++] = parse_action(p, z, start, errs)))
  961.                 return nil;
  962.         }
  963.     }
  964. }
  965.  
  966. /*    parse_arg parses a message argument, which must be either an integer
  967.     or a '"'-delimited string.  The args are the same as parse_seq, with
  968.     one addition:
  969.         result        a pointer to where the result should be stored
  970.  
  971.     Only a few escape sequences are recognized: \n, \t, \\, and \".  It
  972.     would be easy to add more.
  973.  
  974.     If there are no errors, the result (coerced to an int) will be stored
  975.     in *result and parse_arg will return true; otherwise it returns false.
  976. */
  977.  
  978. BOOL parse_arg(int *result, const char **p, NXZone *z,
  979.                const char *start, ErrorStream *errs)
  980. {
  981.     char arg[MAX_STRING_LENGTH];
  982.     int arg_length = 0;
  983.     char c;
  984.     char *copied_arg;
  985.  
  986.     c = skip_whitespace(p);
  987.     if (DIGIT(c) || (c == '-') || (c == '+'))
  988.         *result = strtol(*p, p, 0);        // ought to check for overflow...
  989.     else if (c == '"') {
  990.         while (1) {
  991.             c = *++*p;
  992.             switch (c) {
  993.             case 0:
  994.                 report_syntax_error(" (unterminated string)", *p, start, errs);
  995.                 return NO;
  996.             case '"':
  997.                 ++*p;
  998.                 goto at_end;
  999.             case '\\':
  1000.                 c = *++*p;
  1001.                 switch (c) {
  1002.                 case 'n':    c = '\n'; break;
  1003.                 case 't':    c = '\t'; break;
  1004.                 case '\\':
  1005.                 case '"':              break;
  1006.                 default:
  1007.                     report_syntax_error(" (unknown escape sequence)",
  1008.                                         *p, start, errs);
  1009.                     return NO;
  1010.                 }
  1011.             }
  1012.             if (arg_length >= MAX_STRING_LENGTH) {
  1013.                 report_syntax_error(" (string too long)", *p, start, errs);
  1014.                 return NO;
  1015.             }
  1016.             arg[arg_length++] = c;
  1017.         }
  1018.     at_end:
  1019.         copied_arg = NXZoneMalloc(z, arg_length+1);
  1020.         memcpy(copied_arg, arg, arg_length);
  1021.         copied_arg[arg_length] = '\0';
  1022.         *result = (int)copied_arg;
  1023.     } else {
  1024.         report_syntax_error("", *p, start, errs);
  1025.         return NO;
  1026.     }
  1027.     return YES;
  1028. }
  1029.  
  1030. /*    parse_msg parses a single message action, such as
  1031.             replaceSel:"foobar" length:3
  1032.     The args and result are the same as for parse_seq.
  1033. */
  1034.  
  1035. XTAction *parse_msg(const char **p, NXZone *z,
  1036.                     const char *start, ErrorStream *errs)
  1037. {
  1038.     char sel_name[MAX_SELECTOR_LENGTH];
  1039.     int args[MAX_ARGS];
  1040.     int sel_length = 0;
  1041.     int num_args = 0;
  1042.     char c;
  1043.     SEL sel;
  1044.     char *error;
  1045.  
  1046.     c = **p;
  1047.     while (1) {
  1048.         sel_name[sel_length++] = c;
  1049.         if (sel_length >= MAX_SELECTOR_LENGTH) {
  1050.             error = " (selector too long)";
  1051.             goto syntax_error;
  1052.         }
  1053.         ++*p;
  1054.         if (c == ':') {
  1055.             if (num_args >= MAX_ARGS) {
  1056.                 error = " (too many args)";
  1057.                 goto syntax_error;
  1058.             }
  1059.             if (!parse_arg(&args[num_args++], p, z, start, errs))
  1060.                 return nil;
  1061.             skip_whitespace(p);
  1062.         }
  1063.         c = **p;
  1064.         if (!(ALPHA(c) || DIGIT(c) || c == ':'))
  1065.             break;
  1066.     }
  1067.     sel_name[sel_length] = '\0';
  1068.     sel = sel_getUid(sel_name);
  1069.     if (sel == 0) {
  1070.         error = " (unknown selector)";
  1071.         goto syntax_error;
  1072.     }
  1073.     return num_args == 0
  1074.                 ? [[XTMsg0Action allocFromZone:z] initSel:sel]
  1075.         : num_args == 1
  1076.                    ? [[XTMsg1Action allocFromZone:z] initSel:sel arg:args[0]]
  1077.         : [[XTMsg2Action allocFromZone:z] initSel:sel arg:args[0] arg:args[1]];
  1078.  
  1079. syntax_error:
  1080.     report_syntax_error(error, *p, start, errs);
  1081.     return nil;
  1082. }
  1083.  
  1084. /*    parse_action parses an action, which currently must be either a message
  1085.     to be sent to the XText object or a sequence of actions.  The args are
  1086.     the same as parse_seq.
  1087. */
  1088.  
  1089. XTAction *parse_action(const char **p, NXZone *z,
  1090.                        const char *start, ErrorStream *errs)
  1091. {
  1092.     char c;
  1093.  
  1094.     c = skip_whitespace(p);
  1095.     if (ALPHA(c))
  1096.         return parse_msg(p, z, start, errs);
  1097.     if (c == '{')
  1098.         return parse_seq(p, z, start, errs);
  1099.     report_syntax_error(((c == 0) ? " (unexpected end)" : ""),
  1100.                         *p, start, errs);
  1101.     return nil;
  1102. }
  1103.  
  1104. /*    parse_keys parses a specification of the keys an action is to be bound
  1105.     to.  A specification is a ','-separated sequence, terminated by a '=',
  1106.     where each element is zero or more modifiers ('c', 's', 'a', or 'm')
  1107.     followed by either a hex key code or ' followed by the character generated
  1108.     by the key.  In the latter case there may be several keys that generate
  1109.     the character; each is added to the set.  If there are no errors, the
  1110.     key codes are stored in keys and true is returned; otherwise false is
  1111.     returned.  If there are fewer than MAX_KEYS keys, the first unused entry
  1112.     in keys is set to 0 (which happens to be an invalid key code).
  1113. */
  1114.  
  1115. BOOL parse_keys(keySet keys, const char **p,
  1116.                 const char *start, ErrorStream *errs)
  1117. {
  1118.     int num_keys = 0;
  1119.     int key = 0;
  1120.     int i;
  1121.     char c;
  1122.     BOOL found_one;
  1123.     char *error;
  1124.  
  1125.     while (1) {
  1126.         c = skip_whitespace(p);
  1127.         found_one = NO;
  1128.         switch (c) {
  1129.         case 'c': key |= 1; break;
  1130.         case 's': key |= 2; break;
  1131.         case 'a': key |= 4; break;
  1132.         case 'm': key |= 8; break;
  1133.         case '0': case '1': case '2': case '3': case '4': case '5':
  1134.             key += (c - '0') << 8;
  1135.             c = *++*p;
  1136.             if (DIGIT(c))
  1137.                 key += (c - '0') << 4;
  1138.             else if ((c >= 'a') && (c <= 'f'))
  1139.                 key += (10 + c - 'a') << 4;
  1140.             else if ((c >= 'A') && (c <= 'F'))
  1141.                 key += (10 + c - 'A') << 4;
  1142.             else {
  1143.                 error = "";
  1144.                 goto syntax_error;
  1145.             }
  1146.             if (num_keys >= MAX_KEYS) {
  1147.                 error = " (too many keys)";
  1148.                 goto syntax_error;
  1149.             }
  1150.             keys[num_keys++] = key;
  1151.             found_one = YES;
  1152.             break;
  1153.         case '\'':
  1154.             c = *++*p;
  1155.             found_one = NO;
  1156.             // skip over the first couple of keys, which don't exist and/or
  1157.             // don't generate ascii codes
  1158.             for (i = 6; i < (2*MAPPED_KEYS); ++i) {
  1159.                 // if a key generates the same character shifted and unshifted,
  1160.                 // don't add them both.
  1161.                 if ((ascii[i] == c) && (((i&1) == 0) || (ascii[i-1] != c))) {
  1162.                     if (num_keys >= MAX_KEYS) {
  1163.                         error = " (too many keys)";
  1164.                         goto syntax_error;
  1165.                     }
  1166.                     keys[num_keys++] = ((i&0xfe) << 3) | key | ((i&1) << 1);
  1167.                     found_one = YES;
  1168.                 }
  1169.             }
  1170.             if (!found_one) {
  1171.                 error = " (no key for this char)";
  1172.                 goto syntax_error;
  1173.             }
  1174.             break;
  1175.         default:
  1176.             error = "";
  1177.             goto syntax_error;
  1178.         }
  1179.         ++*p;
  1180.         if (found_one) {
  1181.             c = skip_whitespace(p);
  1182.             ++*p;
  1183.             if (c == ',')
  1184.                 {}                    // go back for more
  1185.             else if (c == '=') {
  1186.                 if (num_keys < MAX_KEYS)
  1187.                     keys[num_keys] = 0;
  1188.                 return YES;
  1189.             } else {
  1190.                 error = "";
  1191.                 goto syntax_error;
  1192.             }
  1193.         }
  1194.     }
  1195.     
  1196. syntax_error:
  1197.     report_syntax_error(error, *p, start, errs);
  1198.     return NO;
  1199. }                    
  1200.  
  1201. @implementation XTDispatchAction(parsing)
  1202.  
  1203. /*    Finally, here's the method we've been preparing to implement.
  1204.     Note that any XTActions generated will be allocated in the same
  1205.     zone as the dispatch action.
  1206. */
  1207.  
  1208. - addBindings:(const char *)bindings estream:errs
  1209. {
  1210.     keySet keys;
  1211.     char c;
  1212.     const char *cp = bindings;
  1213.     XTAction *a;
  1214.     NXZone *z = [self zone];
  1215.     int i;
  1216.  
  1217.     if (!errs) errs = [ErrorStream default];
  1218.     while (1) {
  1219.         c = skip_whitespace(&cp);
  1220.         if (c == 0)
  1221.             return self;
  1222.         if (c == ';')
  1223.             ++cp;
  1224.         else {
  1225.             if (!parse_keys(keys, &cp, bindings, errs))
  1226.                 return self;
  1227.             if (!(a = parse_action(&cp, z, bindings, errs)))
  1228.                 return self;
  1229.             for (i = 0; i < MAX_KEYS; ++i) {
  1230.                 if (keys[i])
  1231.                     [self bindKey:keys[i] toAction:a estream:errs];
  1232.                 else
  1233.                     break;
  1234.             }
  1235.         }
  1236.     }
  1237. }
  1238.  
  1239. @end
  1240. // (end of XTAction_parser.m)
  1241.